package router

import (
	"fmt"
	"os"
	"time"

	"gitee.com/liukexing/isrn/api/network"

	"gitee.com/liukexing/isrn/api/advanced"
)

import (
	"encoding/base64"

	ci "gitee.com/liukexing/isrn/go-libp2p-crypto"
	ma "github.com/multiformats/go-multiaddr"
	manet "github.com/multiformats/go-multiaddr-net"
)

const ()

var (
	lport       int = 20000
	connectlist map[string]*ConnectInfo
)

func getPeerPublicKey(peerid string) (ci.PubKey, error) {
	id := network.IDCommand{*sh}
	r, err := id.ID(peerid)
	if err != nil {
		return nil, err
	}
	publicBytes, err := base64.StdEncoding.DecodeString(r.PublicKey)
	if err != nil {
		return nil, err
	}
	return ci.UnmarshalPublicKey(publicBytes)
}

func updateConnectList() {
	for {
		swarm := &network.SwarmCommand{*sh}
		peers, err := swarm.Peers(false, false)
		if err != nil {
			fmt.Fprintf(os.Stdout, "Update Peers Failed:%s\n", err.Error())
			return
		}
		for _, v := range peers.Peers {
			if _, exist := connectlist[v.Peer]; !exist {
				connectlist[v.Peer] = &ConnectInfo{
					NodeId:  v.Peer,
					ReqChan: make(chan []byte, 5),
					ReqStat: true,
					ResChan: make(chan []byte, 5),
					ResStat: true,
					Active:  false,
					Created: false,
				}
				if pubKey, err := getPeerPublicKey(v.Peer); err != nil {
					fmt.Fprintf(os.Stdout, "Peer '%s'  get pubkey failed:%s\n", v.Peer, err.Error())
				} else {
					connectlist[v.Peer].RCyprto.Pk = pubKey
					if err = createConn(connectlist[v.Peer]); err != nil {
						fmt.Fprintf(os.Stdout, "createConn(%s) Error:%s\n", v.Peer, err.Error())
					}
				}
				connectlist[v.Peer].Created = true
			}
		}
		time.Sleep(time.Minute * 10)

	}
}

func updateConnectTick() {
	var err error
	for {
		for _, v := range connectlist {
			if v.Active {
				//tick connect
				if err = tickMsgSend(v); err != nil {
					v.Active = false
					fmt.Fprintf(os.Stdout, "tickMsgSend Error:%s\n", err.Error())
				}
			}
		}
		time.Sleep(time.Second * 10)
	}
}

func tickMsgSend(conn *ConnectInfo) error {
	if time.Since(conn.LastTick) > time.Minute*5 {
		return fmt.Errorf("Tick TimeOut")
	}
	if time.Since(conn.LastTick) > time.Second*30 {
		return sendTickRequest(conn)
	}
	return nil
}

func createConn(conn *ConnectInfo) error {
	p2p := advanced.P2pCommand{*sh}
	v, err := p2p.StreamDial(conn.NodeId, "Route", fmt.Sprintf("/ip4/127.0.0.1/tcp/%d", lport))
	if err != nil {
		return err
	}

	maddr, err := ma.NewMultiaddr(v.Address)
	if err != nil {
		return err
	}

	var dialConn manet.Conn

	dialConn, err = manet.Dial(maddr)
	if err != nil {
		return err
	}
	fmt.Fprintf(os.Stdout, "Start Dial to %s:%s\n", conn.NodeId, v.Address)
	conn.Conn = dialConn
	conn.LastTick = time.Now()
	conn.Active = true
	go receiverListen(conn)
	go sendListen(conn)
	if err = sendSwapRequest(conn); err != nil {
		closeListen(conn)
		return err
	}
	lport++
	return nil
}

func connMessagePraise() {
	for {
		msgcnt := 0
		for k, v := range connectlist {
			if v.Active && len(v.ReqChan) > 0 {
				data := <-v.ReqChan
				dataDecode(data, v)
				msgcnt++
			}
			if v.Created && (!v.Active || time.Since(v.LastTick) > time.Minute*5) {
				closeListen(v)
				delete(connectlist, k)
			}
		}

		if msgcnt == 0 {
			time.Sleep(time.Millisecond * 10)
		}
	}
}

func startConnect() error {
	connectlist = make(map[string]*ConnectInfo)
	go updateConnectList()
	go updateConnectTick()
	go connMessagePraise()
	return nil
}
